package com.oracle.calllog.producer;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.UUID;

/**
 * 通话日志数据仿真系统的实现类标准版,无需清洗垃圾数据<BR>
 * 本仿真系统模拟格式标准如下:<BR>
 * <B>通话编号 主叫人所在城市 主叫人网络类型(2G|3G|4G|5G) 主叫人移动卡业务类型(全球通|神州行|动感地带) 主叫号码 主叫人姓名 被叫人所在城市 被叫人网络类型(2G|3G|4G|5G) 被叫人运营商类型(移动|联通|电信)被叫号码 被叫人姓名 通话开始时间 通话时长 通话类型(接通|未接通|语音信箱)</B><BR>
 * 本系统标准格式的示例如下所示:<BR>
 * <I>abcd1234efgh5678ijkl9012mnop3456 110000 5G M-Zone 18888888888 张飞 110000 4G CMCC 16666666666 关羽 2020-01-09_08:09:10 0090 CONNECT_CALL</I><BR>
 * @author Mr.Chen
 * @version 1.2.1
 * @time 2020-6-6
 */
public class CallLogsStandardSimulationProducer {

    //用于存放行政区划代码,以便随机获取行政区划代码
    protected List<Object> divisionCodeList;
    //用于存放全部联系人中的前8000条主叫人的移动号码信息
    protected List<Object> callingPartyPhoneNosList;
    //用于存放全部联系人的号码信息
    protected List<Object> allConsumerPhoneNosList;
    //用于存放全部联系人的电话号码与客户卡信息的对应关系
    protected Map<Object, Object> allConsumerInfosMap;
    //用于存放网络类型,由于现实中4G和5G应用更广泛,因此多一些4G数据量,5G数据量次之
    protected String[] netTypes;
    //用于存放接通类型,由于现实中接通电话的通讯场景更多一些,因此多一些接通电话的数据量,未接通电话的数据量次之
    protected String[] connectTypes;
    //用于控制随机数的属性
    protected Random random;
    //用于格式化通话时长的工具对象
    protected DecimalFormat decimalFormat;


    /**
     * 加载存储了数据的属性文件到Properties对象中,并初始化数据容器
     * @param strScale 数据规模，根据取值为any或all决定加载完整数据文件或是规模较小的数据文件
     */
    protected void init(String strScale) {
    	//用于加载行政区划数据文件
    	Properties divisionCodeProps = new Properties();
    	//用于加载全部联系人中的前8000条主叫人的移动卡相关信息的数据文件
    	Properties callingPartyInfoProps = new Properties();
    	//用于加载全部联系人信息的数据文件
    	Properties consumerInfoProps = new Properties();
    	String ext = "";
    	if(strScale.toLowerCase().equals("any")) {
    		ext = "_any";
    	}
        try {
			divisionCodeProps.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("DivisionCode.properties"));
			callingPartyInfoProps.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("CallingPartyInfo"+ext+".properties"));
			consumerInfoProps.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("AllConsumerInfo"+ext+".properties"));
			
			this.divisionCodeList = new ArrayList<Object>(divisionCodeProps.keySet());
			this.callingPartyPhoneNosList = new ArrayList<Object>(callingPartyInfoProps.keySet());
			this.allConsumerPhoneNosList = new ArrayList<Object>(consumerInfoProps.keySet());
			this.allConsumerInfosMap=new HashMap<>();
			this.allConsumerInfosMap.putAll(consumerInfoProps);
			
			netTypes = new String[] {"2G","3G","4G","4G","4G","4G","4G","4G","4G","4G","4G","4G","4G","5G","5G","5G","5G","5G"};
			connectTypes = new String[] {"VOICE_MAIL","CONNECT_CALL","CONNECT_CALL","CONNECT_CALL","CONNECT_CALL","CONNECT_CALL","CONNECT_CALL","CONNECT_CALL","CONNECT_CALL","LOSS_CALL","LOSS_CALL","LOSS_CALL","LOSS_CALL"};
			random = new Random();
			decimalFormat = new DecimalFormat("0000");
        } catch (IOException e) {
			System.err.println("属性数据文件加载出错!");
			e.printStackTrace();
		} finally {
			divisionCodeProps = null;
			callingPartyInfoProps = null;
			consumerInfoProps = null;
			Runtime.getRuntime().gc();
		}
        
    }

    /**
     * 生产随机通话记录数据<br>
     * @param strScale 随机联系人的体量
     * @param strStartDate 字符串格式起始日期,格式为:yyyy-MM-dd
     * @param strEndDate 字符串格式起始日期,格式为:yyyy-MM-dd
     */
    public String product(String strScale, String strStartDate, String strEndDate) {
    	//通话编号
    	String callId = UUID.randomUUID().toString().replace("-", "");
    	//主叫人所在城市
    	String callingDivisionCode = this.generateDivition();
    	//主叫人网络类型(2G|3G|4G|5G)
    	String callingNetType = this.generateNetType();
    	//主叫电话号码
        Object callingParty = this.generateCallingParty(strScale);
        //根据主叫人电话号码获取联系人姓名和运营商类型以及移动卡业务类型的信息数组
        String[] callingPartyInfos = this.getConsumerInfos(callingParty);
        //主叫人
        String callingPartyName = callingPartyInfos[0];
        //主叫人移动卡业务类型(全球通|神州行|动感地带)
        String callingBizType = callingPartyInfos[2];

    	//被叫人所在城市
    	String calledDivisionCode = this.generateDivition();
    	//被叫人网络类型(2G|3G|4G|5G)
    	String calledNetType = this.generateNetType();
    	//被叫电话号码
    	Object calledParty = this.generateCalledParty(callingParty);
        //根据被叫人电话号码获取联系人姓名和运营商类型以及移动卡业务类型的信息数组
        String[] calledPartyInfos = this.getConsumerInfos(calledParty);
    	//被叫人运营商类型
    	String calledCardType = calledPartyInfos[1];
        //被叫人
        String calledPartyName = calledPartyInfos[0];
        
        //通话开始时间
        String callBuildTime = this.generateBuildTime(strStartDate, strEndDate);
        //通话类型(接通|未接通|语音信箱)
        String connectType = this.generateConnectType();

        //根据通话类型确定通话时长
        String callDuration = this.generateCallDuration(connectType);
        
        StringBuilder builder = new StringBuilder();
        
        //格式:通话编码 主叫人所在城市 主叫人网络类型 主叫人移动卡业务类型 主叫号码 主叫人姓名 被叫人所在城市 被叫人网络类型 被叫人运营商类型 被叫号码 被叫人姓名 通话开始时间 通话时长 通话类型
		builder.append(callId+ " ")
				.append(callingDivisionCode+ " ")
        		.append(callingNetType + " ")
	        	.append(callingBizType + " ")
	        	.append(callingParty + " ")
	        	.append(callingPartyName + " ")
	        	.append(calledDivisionCode+ " ")
        		.append(calledNetType + " ")
	        	.append(calledCardType + " ")
	        	.append(calledParty + " ")
	        	.append(calledPartyName + " ")
	        	.append(callBuildTime + " ")
	        	.append(callDuration + " ")
	        	.append(connectType);
        return builder.toString();
    }
    
    /**
     * 生成随机城市编码
     * @return 城市编码
     */
    protected String generateDivition() {
        int divisionIndex = random.nextInt(divisionCodeList.size());
        return String.valueOf(divisionCodeList.get(divisionIndex));
    }
    
    /**
     * 生成随机网络类型(2G|3G|4G|5G)
     * @return 网络类型
     */
    protected String generateNetType() {
        int netTypeIndex = random.nextInt(netTypes.length);
        return netTypes[netTypeIndex];
    }
    
    /**
     * 生成随机主叫人移动电话号码
     * @param strScale 随机联系人的体量
     * @return 移动电话号码
     */
    protected String generateCallingParty(String strScale) {
    	int size = 8000;
    	if(strScale.toLowerCase().equals("any")) {
    		size = 200;
    	}
    	int phoneNoIndex = random.nextInt(size);
        return String.valueOf(this.callingPartyPhoneNosList.get(phoneNoIndex));
    }
    
    /**
     * 生成随机被叫人移动电话号码<BR>
     * 其中:被叫人电话号码不能与主叫人电话号码相同
     * @param callingParty 主叫人手机号码
     * @return 移动电话号码
     */
    protected String generateCalledParty(Object callingParty) {
    	String calledParty = null;
    	while (true) {
        	int phoneNoIndex = random.nextInt(this.allConsumerPhoneNosList.size());
            calledParty = String.valueOf(this.allConsumerPhoneNosList.get(phoneNoIndex));
            if (!String.valueOf(callingParty).equals(calledParty)) 
            	break;
        }
    	return calledParty;
    }
    
    /**
     * 根据手机号码获取联系人姓名和运营商类型的信息数组<BR>
     * 其中:如果联系人是主叫人,则此信息数组中还包含移动卡的业务类型信息
     * @param phoneNo 手机号码
     * @return 联系人姓名和运营商类型[可能包含的主叫人移动卡业务类型]的信息数组
     */
    protected String[] getConsumerInfos(Object phoneNo) {
        return String.valueOf(this.allConsumerInfosMap.get(phoneNo)).split("_");
    }

    /**
     * 根据传入的时间区间，在此范围内随机通话建立的时间<br>
     * startTimeTS + (endTimeTs - startTimeTs) * Math.random();<br>
     * @param strStartDate 字符串格式起始日期,格式为:yyyy-MM-dd
     * @param strEndDate 字符串格式起始日期,格式为:yyyy-MM-dd
     */
    protected String generateBuildTime(String strStartDate, String strEndDate) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date startDate = sdf.parse(strStartDate);
            Date endDate = sdf.parse(strEndDate);

            if (endDate.getTime() <= startDate.getTime()) 
            	return null;
			long randomTS = startDate.getTime() + (long) ((endDate.getTime() - startDate.getTime()) * Math.random());
            Date resultDate = new Date(randomTS);
            sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
            String resultTimeString = sdf.format(resultDate);
            return resultTimeString;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 生成随机通话接通类型(接通|未接通|语音信箱)
     * @return 通话接通类型
     */
    protected String generateConnectType() {
        int connectTypeIndex = random.nextInt(connectTypes.length);
        return connectTypes[connectTypeIndex];
    }
    
    /**
     * 根据通话接通类型生成通话时长,包括三种情况:<BR>
     * 1.如果通话接通类型为语音信箱,则随机通话时长范围:[1,30],单位为秒<BR>
     * 2.如果通话接通类型为接通,则随机通话时长范围:[1,9999],单位为秒<BR>
     * 3.如果通话接通类型为未接通,则通话时长为0
     * @param connectType 手机号码
     * @return 联系人姓名和运营商类型[可能包含的主叫人移动卡业务类型]的信息数组
     */
    protected String generateCallDuration(String connectType) {
    	int duration = 0;
        switch(connectType) {
        case "VOICE_MAIL":
        	//生成语音信箱随机通话时长,单位为秒,范围:[1,30],以0000格式保存
        	duration=random.nextInt(30)+1;
        	break;
        case "CONNECT_CALL":
        	//生成电话接通随机通话时长,单位为秒,范围:[1,9999],以0000格式保存
        	duration=random.nextInt(9998)+1;
        	break;
        case "LOSS_CALL":
        	//电话未接通通话时长为0
        	duration=0;
        	break;
        }
        //格式化通话时长
        return decimalFormat.format(duration);
    }
    
    
    /**
     * 将数据写入到文件中
     * @param strLogFilePath 将生成数据写入到的文件位置
     * @param strScale 随机联系人的体量
     * @param strStartDate 字符串格式起始日期,格式为:yyyy-MM-dd
     * @param strEndDate 字符串格式起始日期,格式为:yyyy-MM-dd
     */
    public void writeLog(String strLogFilePath, String strScale, String strStartDate, String strEndDate) {
    	OutputStreamWriter osw = null;
        try {
            osw = new OutputStreamWriter(new FileOutputStream(strLogFilePath), "UTF-8");
            while (true) {
            	//每秒随机产生0-100条通话记录
                Thread.sleep(1500);
                StringBuffer buffer = new StringBuffer();
                for(int i=0; i<random.nextInt(100); i++) {
	                String log = product(strScale, strStartDate, strEndDate);
	                buffer.append(log+"\n");
                }
                //一定要手动flush才可以确保每条数据都写入到文件一次，但这样性能不算好
                System.out.println(buffer.toString());
                osw.write(buffer.toString());
                osw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e2) {
            e2.printStackTrace();
        } finally {
			if(osw!=null)
				try {
					osw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
    }

    
    
    /**
     * 仿真系统运行入口主方法
     * @param args 数据存储路径[ 通话数据生成范围的起始日期 通话数据生成范围的截止日期]
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        if (args == null || args.length <= 0) {
            System.out.println("no arguments");
            return;
        }
        //String logPath = "/tmp/calllog.csv";
        String logPath = args[0];
        String scale = "any";
        String startDate = "2020-01-01";
        //默认终止时间点为当前时间的下一天
        long endTime = System.currentTimeMillis()+24*60*60*1000;
        String endDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date(endTime));
        
        // 传递两个参数时，数据规模根据参数决定(any|all)，否则默认为any
        if(args.length==2)
        	scale = args[1];
        // 传递三个参数时，随机通话时间数据的起始时间点使用传递的参数，终止时间点使用默认值
        if(args.length==3)
        	startDate = args[2];
        //传递四个以上参数时，随机通话时间数据的起始和终止时间点都使用传递的参数
        if(args.length>=4) 
        	endDate = args[3];
        
        CallLogsStandardSimulationProducer productLog = new CallLogsStandardSimulationProducer();
        productLog.init(scale);
        productLog.writeLog(logPath, scale, startDate, endDate);
    }
}

